// Sorting ripped from Paul Tingey
// Globalization ripped from Gerd Klevesaat,
// http://www.codeguru.com/Csharp/Csharp/cs_controls/propertygrid/comments.php/c4795
// Dynamic behaviour designed/implemented by George Soules and Wout de Zeeuw.
// (c) 2004 Wout de Zeeuw

using System;
using System.Collections;
using System.ComponentModel;
using System.Reflection;
using System.Resources;

namespace CaveArt.UIcomp
{
	/// <summary>
	/// This class makes it possible to: 
	/// globalize property display names and desriptions,
	/// sort properties and set the DisplayName to a property with an attribute.
	/// Also all property characteristics can be changed dynamically if needed.
	/// </summary>
	/// <remarks>
	/// Set a class's <see cref="TypeConverter"/> to this class to enable
	/// globalization and dynamic property characteristics.
	/// <para/>
	/// The display name and description for a property are obtained
	/// from a resource file if specified by the <see cref="GlobalizedPropertyAttribute"/>.
	/// The resource file is expected to be named namespace.classname.resources.
	/// Resources are expected to be named PropertyName.DisplayName,
	/// PropertyName.Description PropertyName.Category (all optional).
	/// Also sorts using the <see cref="PropertyOrderAttribute"/> attribute.
	/// <para/>
	/// Specify <see cref="DisplayNameAttribute"/> for simpler usage (not globalized).
	/// <para/>
	/// If a property has the <see cref="PropertyAttributesProviderAttribute"/> attribute
	/// then the delegate specified is called for its runtime <see cref="PropertyAttributes"/>.
	/// <para/>
	/// See also 
	/// <seealso cref="DisplayNameAttribute"/>
	/// <seealso cref="GlobalizedPropertyAttribute"/>
	/// <seealso cref="GlobalizedTypeAttribute"/>
	/// <seealso cref="PropertyAttributesProviderAttribute"/>
	/// <seealso cref="PropertyOrderAttribute"/>
	/// </remarks>
	public class CaveArtTypeConverter : ExpandableObjectConverter 
	{
//		public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
//		{
//			if ( destinationType == typeof(string) )
//			{
//				//return String.Empty;
//			}
//
//			return base.ConvertTo (context, culture, value, destinationType);
//		}

		public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
        {
			PropertyDescriptorCollection		baseProps	= TypeDescriptor.GetProperties(value, attributes);
			PropertyDescriptorCollection		sortProps	= new PropertyDescriptorCollection(null);
			ArrayList							opaList		= new ArrayList();
			
			foreach (PropertyDescriptor o in baseProps)
			{
				PropertyAttributes pa = GetPropertyAttributes(o, value);
				
				if (pa.IsBrowsable) 
				{
					opaList.Add(pa);
					sortProps.Add(new PropertyDescriptorEx(o, pa));
				}
			}
			opaList.Sort();
            
			//
            // Build a string list of the ordered names
            //
            ArrayList propertyNames = new ArrayList();
            foreach (PropertyAttributes pa in opaList)
            {
                propertyNames.Add(pa.Name);
            }

            //
            // Pass in the ordered list for the PropertyDescriptorCollection to sort by.
			// (Sorting by passing a custom IComparer somehow doesn't work.
            //
            return sortProps.Sort( (string[]) propertyNames.ToArray(typeof(string)) );
		}
		
		/// <summary>
		/// Get property attributes for given property descriptor and target object.
		/// </summary>
		private PropertyAttributes GetPropertyAttributes(PropertyDescriptor pd, object target ) 
		{
			PropertyAttributes pa = new PropertyAttributes(pd.Name);
            
			string resourceBaseName = null;
			string displayName		= null;
		    string displayNameResourceName = null;
		    string descriptionResourceName = null;
		    string categoryResourceName = null;
			ResourceManager rm = null;

			//
			// First fill pa with statically defined information.
			//
			foreach( Attribute attribute in pd.Attributes )
			{
			    Type type = attribute.GetType();
    		    
				// If there's a DisplayNameAttribute defined, use that DisplayName.
				if ( type.Equals(typeof(DisplayNameAttribute)) )
				{
					displayName = ((DisplayNameAttribute) attribute).DisplayName;
				} 
				else if ( type.Equals(typeof(GlobalizedPropertyAttribute)) )
				{
					// Get specific info about where to find resources for given property.
					displayNameResourceName = ((GlobalizedPropertyAttribute)attribute).DisplayNameId;
					descriptionResourceName = ((GlobalizedPropertyAttribute)attribute).DescriptionId;
					categoryResourceName	= ((GlobalizedPropertyAttribute)attribute).CategoryId;
					resourceBaseName		= ((GlobalizedPropertyAttribute)attribute).BaseName;
				} 
				else if (type.Equals(typeof(PropertyOrderAttribute))) 
				{
					pa.Order = ((PropertyOrderAttribute) attribute).Order;
				}
				else if (type.Equals(typeof(FieldIndexAttribute))) 
				{
					pa.FieldIndex = ((FieldIndexAttribute) attribute).FieldIndex;
				}

				#if READONLY_TEST
				else if (type.Equals(typeof(ReadOnlyAttribute)))
				{
					pa.ReadOnly = ((ReadOnlyAttribute) attribute).ReadOnly;
				}
				#endif
			}
			
			if (resourceBaseName == null) 
			{
			    foreach ( Attribute attribute in pd.ComponentType.GetCustomAttributes(true) ) 
				{
			        if ( attribute.GetType().Equals(typeof(GlobalizedTypeAttribute)) ) 
					{
    				    // Get specific info about where to find resources for given Type.
			            resourceBaseName = ((GlobalizedTypeAttribute)attribute).BaseName;
			        }
			    }
			    if (resourceBaseName == null) 
				{
    			    resourceBaseName = pd.ComponentType.Namespace + "." + pd.ComponentType.Name;
			    }
			}

		    // See if at least the culture neutral resources are there.
		    // If not, disable globalization
		    Assembly assembly = pd.ComponentType.Assembly;
		    if (assembly.GetManifestResourceInfo(resourceBaseName + ".resources") == null) 
			{
		        rm = null;
		    } 
			else 
			{
    		    rm = new ResourceManager(resourceBaseName, assembly);
		        if (displayNameResourceName == null) 
				{
            		displayNameResourceName = pd.DisplayName + ".DisplayName";
		        }
		        if (descriptionResourceName == null) 
				{
            		descriptionResourceName = pd.DisplayName + ".Description";
		        }
		        if (categoryResourceName == null) 
				{
		            categoryResourceName = pd.Category + ".Category";
		        }
		    }
			
			// Display name.
			if (rm != null) 
			{
			    pa.DisplayName = rm.GetString(displayNameResourceName);
			} 
			else 
			{
			    pa.DisplayName = null;
			}
			if (pa.DisplayName == null) 
			{
		        pa.DisplayName = displayName;
		    }
			if (pa.DisplayName == null)
		    {
		        pa.DisplayName = pd.DisplayName;
			}
			
			// Description.
			if (rm != null) 
			{
			    pa.Description = rm.GetString(descriptionResourceName);
			} 
			else 
			{
			    pa.Description = null;
			}
			if (pa.Description == null)
		    {
		        pa.Description = pd.Description;
			}
			
			// Category.
			if (rm != null) 
			{
			    pa.Category = rm.GetString(categoryResourceName);
			} 
			else 
			{
			    pa.Category = null;
			}
			if (pa.Category == null)
		    {
		        pa.Category = pd.Category;
			}
			
			// IsReadonly.
			pa.IsReadOnly = pd.IsReadOnly;
			
			// IsBrowsable.
			pa.IsBrowsable = pd.IsBrowsable;
			
			//
			// Now let target be able to override each of these property attributes
			// dynamically.
			//

			PropertyAttributesProviderAttribute attr = (PropertyAttributesProviderAttribute) pd.Attributes[typeof(PropertyAttributesProviderAttribute)];
			if (attr != null) 
			{
				MethodInfo method = attr.GetPropertyAttributesProvider(target);
				if (method != null) {
					method.Invoke(target, new object[] {pa});
				}
			}
			
			return pa;
		}
	}
}
